/* BattleState.java
 * 
 * Copyright (c) 2009 jmectf
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jmectf.game.state;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.concurrent.Callable;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.image.Texture.CombinerScale;
import com.jme.input.ChaseCamera;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.CameraNode;
import com.jme.scene.Node;
import com.jme.scene.Skybox;
import com.jme.scene.Spatial;
import com.jme.scene.Spatial.CullHint;
import com.jme.scene.state.CullState;
import com.jme.scene.state.TextureState;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.TextureManager;
import com.jme.util.export.binary.BinaryImporter;
import com.jmectf.game.handler.BattleHandler;
import com.jmectf.game.vehicle.Vehicle;
import com.jmectf.spatial.Minimap;
import com.jmex.audio.AudioSystem;
import com.jmex.audio.AudioTrack;
import com.jmex.audio.MusicTrackQueue;
import com.jmex.audio.MusicTrackQueue.RepeatType;
import com.jmex.game.StandardGame;
import com.jmex.game.state.DebugGameState;
import com.jmex.game.state.GameStateManager;
import com.jmex.game.state.load.LoadingGameState;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.ImageBasedHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;

public class BattleState extends DebugGameState {
	/**
	 * Enumeration <code>Environment</code> provides values for possible skyboxes.
	 */
	enum Environment {
		desert,
		devilpunch,
		emerald,
		frozen;
	}

	TerrainPage tp;
	CameraNode camNode;
	Vector3f normal;
	StandardGame g;
	Vehicle player;
	ChaseCamera chaser;
	Node model;
	private float agl;
	Skybox skybox;
	Node miniMap;
	Minimap mm;
	boolean toggleMiniMap;
	String skyboxType;
	boolean menuOpen;

	/**
	 * Constructor creates a new <code>jmectfGameState</code> object. This constructor
	 * initializes the skybox, the terrain, the chase camera, the player, the input
	 * handling, etc.
	 * @param g the <code>StandardGame</code> object that this game state is in.
	 */
	public BattleState(StandardGame g) {
		this.g = g;
		menuOpen = false;
		final LoadingGameState loadState = new LoadingGameState();
		GameStateManager.getInstance().attachChild(loadState);
		loadState.setActive(true);
		loadState.setProgress(0f, "Building lighting...");
		getRootNode().setRenderQueueMode(Renderer.QUEUE_OPAQUE);
		buildLighting();
		int s = Environment.values().length;
		int choice = (int) (Math.random() * s);
		skyboxType = Environment.values()[choice].name();
		g.getCamera().setFrustum(1.0f, 1000.0f, -0.55f, 0.55f, 0.4125f, -0.4125f);
		g.getCamera().update();
		camNode = new CameraNode("Camera Node", g.getCamera());
		camNode.setLocalTranslation(new Vector3f(0, 250, -20));
		camNode.updateWorldData(0);
		normal = new Vector3f();
		getRootNode().attachChild(camNode);
		CullState cs = g.getDisplay().getRenderer().createCullState();
		getRootNode().setCullHint(CullHint.Never);
		cs.setEnabled(true);
		getRootNode().setRenderState(cs);
		loadState.setProgress(.1f, "Building terrain...");
		Vector3f terrainScale = new Vector3f(10,1,10);
		URL url = getClass().getResource("/com/jmectf/data/heightmap/test.jpg");
		BufferedImage i = null;
		try {
			i = ImageIO.read(url);
		} catch (IOException e1) {
			e1.printStackTrace();
			System.exit(1);
		}
		ImageBasedHeightMap heightMap2 = new ImageBasedHeightMap(i);
		tp = new TerrainPage("Terrain", 33, heightMap2.getSize(), terrainScale,
				heightMap2.getHeightMap());
		tp.setDetailTexture(1, 128);
		loadState.setProgress(.2f, "Texturing...");
		ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap2);
		pt.addTexture(new ImageIcon(BattleState.class.getClassLoader().getResource(
		"jmetest/data/texture/grassb.png")), -128, 0, 128);
		pt.addTexture(new ImageIcon(BattleState.class.getClassLoader().getResource(
		"jmetest/data/texture/dirt.jpg")), 0, 128, 255);
		pt.addTexture(new ImageIcon(BattleState.class.getClassLoader().getResource(
		"jmetest/data/texture/highest.jpg")), 128, 255, 384);
		pt.createTexture(512);
		TextureState ts = g.getDisplay().getRenderer().createTextureState();
		ts.setEnabled(true);
		Texture t1 = TextureManager.loadTexture(
				pt.getImageIcon().getImage(),
				Texture.MinificationFilter.Trilinear,
				Texture.MagnificationFilter.Bilinear,
				true);
		ts.setTexture(t1, 0);
		Texture t2 = TextureManager.loadTexture(BattleState.class.getClassLoader().
				getResource(
				"jmetest/data/texture/Detail.jpg"),
				Texture.MinificationFilter.Trilinear,
				Texture.MagnificationFilter.Bilinear);
		ts.setTexture(t2, 1);
		t2.setWrap(Texture.WrapMode.Repeat);
		t1.setApply(Texture.ApplyMode.Combine);
		t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);
		t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
		t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
		t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);
		t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
		t1.setCombineScaleRGB(CombinerScale.One);
		t2.setApply(Texture.ApplyMode.Combine);
		t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);
		t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
		t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
		t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);
		t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
		t2.setCombineScaleRGB(CombinerScale.One);
		tp.setRenderState(ts);
		tp.updateRenderState();
		tp.updateModelBound();
		getRootNode().attachChild(tp);
		loadState.setProgress(.3f, "Building player...");
		buildPlayer(new Vector3f(100, 100, 100));
		loadState.setProgress(.4f, "Building camera...");
		buildChaseCamera();
		loadState.setProgress(.5f, "Building input...");
		buildMiniMap();
		initKeyBindings();
		loadState.setProgress(.6f, "Building skybox...");
		try {
			buildSkybox();
		} catch (IOException e) {
			e.printStackTrace();
		}
		getRootNode().updateRenderState();
		getRootNode().updateGeometricState(0, true);
		
		input = new BattleHandler(player, g.getSettings().getRenderer());
		loadState.setProgress(.7f, "Building audio...");
		// build audio (Requiem of a Dream by Clint Mansell)
		if(g.getSettings().isMusic()) {
			try {
				AudioTrack track = AudioSystem.getSystem().createAudioTrack(
						BattleState.class.getClassLoader().getResource(
						"com/jmectf/data/audio/bgm.ogg"), true);
				MusicTrackQueue queue = AudioSystem.getSystem().getMusicQueue();
				queue.setCrossfadeinTime(0);
				queue.setRepeatType(RepeatType.ALL);
				queue.addTrack(track);
				queue.play();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		loadState.setProgress(1.0f, "Done...");
	}

	private void buildChaseCamera() {
		HashMap<String, Object> props = new HashMap<String, Object>();
		props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6");
		props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3");
		props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+45 * FastMath.DEG_TO_RAD);
		props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(5, 0, 30 * FastMath.DEG_TO_RAD));
		props.put(ChaseCamera.PROP_DAMPINGK, "4");
		props.put(ChaseCamera.PROP_SPRINGK, "9");
		chaser = new ChaseCamera(g.getCamera(), player, props);
		chaser.setMaxDistance(100);
		chaser.setMinDistance(30);
	}

	private void buildLighting() {
		DirectionalLight dr = new DirectionalLight();
		dr.setEnabled(true);
		dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
		dr.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
		dr.setDirection(new Vector3f(0.5f, -0.5f, 0).normalizeLocal());
		getLightState().attach(dr);
		getLightState().setTwoSidedLighting(true);
	}

	private void buildMiniMap() {
		mm = new Minimap(g);
		miniMap = new Node("MiniMap");
		miniMap.attachChild(mm);
		getRootNode().attachChild(miniMap);
	}

	private void buildPlayer(Vector3f position) {
		Spatial model = null;
		try {
			URL path = getClass().getResource("/com/jmectf/data/tank.jme");
			BinaryImporter importer = new BinaryImporter();
			model = (Spatial)importer.load(path.openStream());
			model.setModelBound(new BoundingBox());
			model.updateModelBound();
			model.setLocalScale(2.0f);
			model.rotateUpTo(new Vector3f(0.0f, 0.0f, -1.0f));
			model.rotateUpTo(new Vector3f(.5f , 0.0f, 0.0f));
		} catch (IOException e) {
			System.out.println("Could not load model.");
			e.printStackTrace();
			System.exit(0);
		}
		player = new Vehicle("Player Node", model);
		player.setAcceleration(25);
		player.setBraking(10);
		player.setTurnSpeed(2.5f);
		player.setWeight(25);
		player.setMaxSpeed(40);
		player.setMinSpeed(15);
		player.setLocalTranslation(position);
		getRootNode().attachChild(player);
		player.setLocalTranslation(new Vector3f(100, tp.getHeight(100, 100), 100));
		getRootNode().updateRenderState();
		getRootNode().updateGeometricState(0, true);
		agl = ((BoundingBox)player.getWorldBound()).yExtent;
		player.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
	}

	private void buildSkybox() throws IOException {
		skybox = new Skybox("skybox", 10, 10, 10);
		Texture north = TextureManager.loadTexture(getClass().getResource("/com/jmectf/data/env/" + skyboxType + "/" + skyboxType + "_bk.jpg"), Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture south = TextureManager.loadTexture(getClass().getResource("/com/jmectf/data/env/" + skyboxType + "/" + skyboxType + "_ft.jpg"), Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture east = TextureManager.loadTexture(getClass().getResource("/com/jmectf/data/env/" + skyboxType + "/" + skyboxType + "_rt.jpg"), Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture west = TextureManager.loadTexture(getClass().getResource("/com/jmectf/data/env/" + skyboxType + "/" + skyboxType + "_lf.jpg"), Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture top = TextureManager.loadTexture(getClass().getResource("/com/jmectf/data/env/" + skyboxType + "/" + skyboxType + "_up.jpg"), Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture bottom = TextureManager.loadTexture(getClass().getResource("/com/jmectf/data/env/" + skyboxType + "/" + skyboxType + "_dn.jpg"), Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		skybox.setTexture(Skybox.Face.North, north);
		skybox.setTexture(Skybox.Face.West, west);
		skybox.setTexture(Skybox.Face.South, south);
		skybox.setTexture(Skybox.Face.East, east);
		skybox.setTexture(Skybox.Face.Up, top);
		skybox.setTexture(Skybox.Face.Down, bottom);
		Callable<?> preload = new Callable<?>() {
			public Object call() throws Exception {
				skybox.preloadTextures();
				return null;
			}
		};
		GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER)
			.enqueue(preload);
		getRootNode().attachChild(skybox);
	}

	private void initKeyBindings() {
		KeyBindingManager manager = KeyBindingManager.getKeyBindingManager();
		manager.set("exit", KeyInput.KEY_ESCAPE);
	}

	/**
	 * <code>update</code> is called every frame and updates the scenegraph, 
	 * scene information, handles terrain following, camera following, skybox following, etc.
	 * @param interpolation time between frames.
	 * @see DebugGameState#update(float)
	 */
	public void update(float interpolation) {
		if(toggleMiniMap) {
			if(miniMap.getChild("MiniMap") == null)
				miniMap.attachChild(mm);
			else
				miniMap.detachAllChildren();
			toggleMiniMap = false;
		}
		input.update(interpolation);
		if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit", false)) {
			MenuState menuState = (MenuState) GameStateManager.getInstance().getChild("MenuState");
			if(menuState != null) {
				if(!menuOpen) {
					menuState.setActive(true);
					menuOpen = true;
				}
				else {
					menuState.setActive(false);
					menuOpen = false;
				}
			}
		}
		chaser.update(interpolation);
		skybox.setLocalTranslation(chaser.getCamera().getLocation());
		Vector3f loc = g.getCamera().getLocation();
		g.getCamera().setLocation(new Vector3f(loc.x, tp.getHeight(new Vector2f(loc.x,loc.z))+5, loc.z));
		tp.getSurfaceNormal(g.getDisplay().getRenderer().getCamera().getLocation(), normal);
		if(normal != null) {
			camNode.rotateUpTo(normal);
			camNode.updateRenderState();
			camNode.updateFromCamera();
		}
		float characterMinHeight = tp.getHeight(player
				.getLocalTranslation())+agl;
		if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
			player.getLocalTranslation().y = characterMinHeight;
		}
		tp.getSurfaceNormal(player.getLocalTranslation(), normal);
		if(normal != null) {
			player.rotateUpTo(normal);
		}
		if(g.getSettings().isMusic())
			AudioSystem.getSystem().update();
		getRootNode().updateGeometricState(interpolation, true);
		getRootNode().updateRenderState();
	}

	/**
	 * @return the player
	 */
	public Vehicle getPlayer() {
		return player;
	}

}
